//
// RS Game Framework
// Copyright © 2009 Jedd Haberstro
// jhaberstro@gmail.com
// 
// $Id:
//

#include "rs/Thread.hpp"
#include "rs/Assert.hpp"
#include "rs/portability/platforms/posix/Implementations.hpp"
#include <limits>
#include <pthread.h>
#include <time.h>

namespace rs
{
    namespace internal
    {   
        struct ThreadFuncData
        {
            ThreadFunction func;
            void* arg;
        };
        
        void* ThreadFuncProxy(void* arg) {
            ThreadFuncData* data = (ThreadFuncData*)arg;
            data->func(data->arg);
            delete data;
            return 0;
        }
    }
    
    ThreadID Thread::GetCurrentID() {
        ThreadID current;
        current.implementation_ = new internal::ThreadIDImplementation(pthread_self());
        return current;
    }
    
    void Thread::Sleep(UInt64 seconds, UInt64 nanoseconds) {
        RS_ASSERT(seconds <= std::numeric_limits< time_t >::max(), "seconds is too large");
        RS_ASSERT(nanoseconds <= std::numeric_limits< long >::max(), "nanoseconds is too large");
        timespec ts;
        ts.tv_sec = static_cast< time_t >(seconds);
        ts.tv_nsec = static_cast< long >(nanoseconds);
        
        timespec remainder;
        while (nanosleep(&ts, &remainder) == -1) {
            ts = remainder;
        }
    }
    
    void Thread::Yield() {
        pthread_yield_np();
    }
    
    Thread::Thread() {
    }
    
    Thread::Thread(ThreadFunction function)
    : func_((ThreadFunction)function, TypeID< void* >::GetID()),
      implementation_(new internal::ThreadImplementation),
      launched_(false) {
    }
    
    Thread::Thread(Thread const& other)
    : func_(other.func_),
      implementation_(other.implementation_),
      launched_(false) {
    }

    Thread::~Thread() {
    }
    
    void Thread::Start(void* arg) {
        RS_ASSERT(implementation_.GetPointer() != 0, "Thread has not been initialized with a thread function");
        RS_ASSERT(launched_ == false, "Thread all ready started");
        internal::ThreadFuncData* data = new internal::ThreadFuncData;
        data->func = func_.func;
        data->arg = arg;
        
        pthread_attr_t attr;
        int err = pthread_attr_init(&attr);
        RS_ASSERTF(err == 0, "Creating pthread attribute failed with error code: %i", err);
        err = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
        RS_ASSERTF(err == 0, "Setting pthread attribute to joinable failed with error code: %i", err);
        
        err = pthread_create(&implementation_->threadId_, &attr, internal::ThreadFuncProxy, (void*)data);
        RS_ASSERTF(err == 0, "Creating pthread failed with error code: %i", err);
        
        err = pthread_attr_destroy(&attr);
        RS_ASSERTF(err == 0, "Destroying pthread attribute failed with error code: %i", err);
        
        launched_ = true;
    }

    void Thread::Join() {
        RS_ASSERT(launched_ == true, "Thread must be started to join");
        void* value = 0;
        int err = pthread_join(implementation_->threadId_, &value);
        RS_ASSERTF(err == 0, "Join pthread failed with error code: %i", err);
        launched_ = false;
    }
    
    void Thread::Detach() {
        RS_ASSERT(launched_ == true, "Thread must be started to detach");
        int err = pthread_detach(implementation_->threadId_);
        RS_ASSERTF(err == 0, "Detaching pthread failed with error code: %i", err);
    }
        
    ThreadID Thread::GetID() const {
        RS_ASSERT(launched_ == true, "Thread must be started to retrieve id");
        ThreadID current;
        current.implementation_ = new internal::ThreadIDImplementation(implementation_->threadId_);
        return current;
    }
    
    void Thread::Swap(Thread& other) {
        std::swap(func_, other.func_);
        implementation_.Swap(other.implementation_);
        std::swap(launched_, other.launched_);
    }
    
    Thread& Thread::operator=(Thread const& other) {
        if (this != &other) {
            Thread(other).Swap(*this);
        }
        
        return *this;
    }
    
    ThreadID::ThreadID()
    : implementation_(new internal::ThreadIDImplementation) {
    }
    
    ThreadID::ThreadID(ThreadID const& other)
    : implementation_(new internal::ThreadIDImplementation(*other.implementation_)) {
    }
    
    ThreadID::~ThreadID() {
        if (implementation_) {
            delete implementation_;
            implementation_ = 0;
        }
    }
    
    ThreadID& ThreadID::operator=(ThreadID const& other) {
        if (this != &other) {
            if (implementation_) {
                delete implementation_;
                implementation_ = 0;
            }
            
            implementation_ = new internal::ThreadIDImplementation(*other.implementation_);
        }
        
        return *this;
    }
    
    Boolean ThreadID::operator==(ThreadID const& other) const {
        return *implementation_ == *other.implementation_;
    }
    
    Boolean ThreadID::operator!=(ThreadID const& other) const {
        return *implementation_ != *other.implementation_;
    }
}